// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>
#include <stdint.h>

#include <algorithm>
#include <cmath>

#include "base/logging.h"
#include "base/macros.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/libpng/png.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColorPriv.h"
#include "third_party/skia/include/core/SkUnPreMultiply.h"
#include "third_party/zlib/zlib.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/skia_util.h"

namespace gfx {

namespace {

    void MakeRGBImage(int w, int h, std::vector<unsigned char>* data)
    {
        data->resize(w * h * 3);
        for (int y = 0; y < h; y++) {
            for (int x = 0; x < w; x++) {
                unsigned char* org_px = &(*data)[(y * w + x) * 3];
                org_px[0] = x * 3; // r
                org_px[1] = x * 3 + 1; // g
                org_px[2] = x * 3 + 2; // b
            }
        }
    }

    // Set use_transparency to write data into the alpha channel, otherwise it will
    // be filled with 0xff. With the alpha channel stripped, this should yield the
    // same image as MakeRGBImage above, so the code below can make reference
    // images for conversion testing.
    void MakeRGBAImage(int w, int h, bool use_transparency,
        std::vector<unsigned char>* data)
    {
        data->resize(w * h * 4);
        for (int y = 0; y < h; y++) {
            for (int x = 0; x < w; x++) {
                unsigned char* org_px = &(*data)[(y * w + x) * 4];
                org_px[0] = x * 3; // r
                org_px[1] = x * 3 + 1; // g
                org_px[2] = x * 3 + 2; // b
                if (use_transparency)
                    org_px[3] = x * 3 + 3; // a
                else
                    org_px[3] = 0xFF; // a (opaque)
            }
        }
    }

    // Creates a palette-based image.
    void MakePaletteImage(int w, int h,
        std::vector<unsigned char>* data,
        std::vector<png_color>* palette,
        std::vector<unsigned char>* trans_chunk = 0)
    {
        data->resize(w * h);
        palette->resize(w);
        for (int i = 0; i < w; ++i) {
            png_color& color = (*palette)[i];
            color.red = i * 3;
            color.green = color.red + 1;
            color.blue = color.red + 2;
        }
        for (int y = 0; y < h; y++) {
            for (int x = 0; x < w; x++) {
                (*data)[y * w + x] = x; // palette index
            }
        }
        if (trans_chunk) {
            trans_chunk->resize(palette->size());
            for (std::size_t i = 0; i < trans_chunk->size(); ++i) {
                (*trans_chunk)[i] = i % 256;
            }
        }
    }

    // Creates a grayscale image without an alpha channel.
    void MakeGrayscaleImage(int w, int h,
        std::vector<unsigned char>* data)
    {
        data->resize(w * h);
        for (int y = 0; y < h; y++) {
            for (int x = 0; x < w; x++) {
                (*data)[y * w + x] = x; // gray value
            }
        }
    }

    // Creates a grayscale image with an alpha channel.
    void MakeGrayscaleAlphaImage(int w, int h,
        std::vector<unsigned char>* data)
    {
        data->resize(w * h * 2);
        for (int y = 0; y < h; y++) {
            for (int x = 0; x < w; x++) {
                unsigned char* px = &(*data)[(y * w + x) * 2];
                px[0] = x; // gray value
                px[1] = x % 256; // alpha
            }
        }
    }

    // User write function (to be passed to libpng by EncodeImage) which writes
    // into a buffer instead of to a file.
    void WriteImageData(png_structp png_ptr,
        png_bytep data,
        png_size_t length)
    {
        std::vector<unsigned char>& v = *static_cast<std::vector<unsigned char>*>(png_get_io_ptr(png_ptr));
        v.resize(v.size() + length);
        memcpy(&v[v.size() - length], data, length);
    }

    // User flush function; goes with WriteImageData, above.
    void FlushImageData(png_structp /*png_ptr*/)
    {
    }

    // Libpng user error function which allows us to print libpng errors using
    // Chrome's logging facilities instead of stderr.
    void LogLibPNGError(png_structp png_ptr,
        png_const_charp error_msg)
    {
        DLOG(ERROR) << "libpng encode error: " << error_msg;
        longjmp(png_jmpbuf(png_ptr), 1);
    }

    // Goes with LogLibPNGError, above.
    void LogLibPNGWarning(png_structp png_ptr,
        png_const_charp warning_msg)
    {
        DLOG(ERROR) << "libpng encode warning: " << warning_msg;
    }

    // Color types supported by EncodeImage. Required because neither libpng nor
    // PNGCodec::Encode supports all of the required values.
    enum ColorType {
        COLOR_TYPE_GRAY = PNG_COLOR_TYPE_GRAY,
        COLOR_TYPE_GRAY_ALPHA = PNG_COLOR_TYPE_GRAY_ALPHA,
        COLOR_TYPE_PALETTE = PNG_COLOR_TYPE_PALETTE,
        COLOR_TYPE_RGB = PNG_COLOR_TYPE_RGB,
        COLOR_TYPE_RGBA = PNG_COLOR_TYPE_RGBA,
        COLOR_TYPE_BGR,
        COLOR_TYPE_BGRA
    };

    // PNG encoder used for testing. Required because PNGCodec::Encode doesn't do
    // interlaced, palette-based, or grayscale images, but PNGCodec::Decode is
    // actually asked to decode these types of images by Chrome.
    bool EncodeImage(const std::vector<unsigned char>& input,
        const int width,
        const int height,
        ColorType output_color_type,
        std::vector<unsigned char>* output,
        const int interlace_type = PNG_INTERLACE_NONE,
        std::vector<png_color>* palette = 0,
        std::vector<unsigned char>* palette_alpha = 0)
    {
        DCHECK(output);

        int input_rowbytes = 0;
        int transforms = PNG_TRANSFORM_IDENTITY;

        switch (output_color_type) {
        case COLOR_TYPE_GRAY:
            input_rowbytes = width;
            break;
        case COLOR_TYPE_GRAY_ALPHA:
            input_rowbytes = width * 2;
            break;
        case COLOR_TYPE_PALETTE:
            if (!palette)
                return false;
            input_rowbytes = width;
            break;
        case COLOR_TYPE_RGB:
            input_rowbytes = width * 3;
            break;
        case COLOR_TYPE_RGBA:
            input_rowbytes = width * 4;
            break;
        case COLOR_TYPE_BGR:
            input_rowbytes = width * 3;
            output_color_type = static_cast<ColorType>(PNG_COLOR_TYPE_RGB);
            transforms |= PNG_TRANSFORM_BGR;
            break;
        case COLOR_TYPE_BGRA:
            input_rowbytes = width * 4;
            output_color_type = static_cast<ColorType>(PNG_COLOR_TYPE_RGBA);
            transforms |= PNG_TRANSFORM_BGR;
            break;
        };

        png_struct* png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
        if (!png_ptr)
            return false;
        png_infop info_ptr = png_create_info_struct(png_ptr);
        if (!info_ptr) {
            png_destroy_write_struct(&png_ptr, NULL);
            return false;
        }

        std::vector<png_bytep> row_pointers(height);
        for (int y = 0; y < height; ++y) {
            row_pointers[y] = const_cast<unsigned char*>(&input[y * input_rowbytes]);
        }

        if (setjmp(png_jmpbuf(png_ptr))) {
            png_destroy_write_struct(&png_ptr, &info_ptr);
            return false;
        }

        png_set_error_fn(png_ptr, NULL, LogLibPNGError, LogLibPNGWarning);
        png_set_rows(png_ptr, info_ptr, &row_pointers[0]);
        png_set_write_fn(png_ptr, output, WriteImageData, FlushImageData);
        png_set_IHDR(png_ptr, info_ptr, width, height, 8, output_color_type,
            interlace_type, PNG_COMPRESSION_TYPE_DEFAULT,
            PNG_FILTER_TYPE_DEFAULT);
        if (output_color_type == COLOR_TYPE_PALETTE) {
            png_set_PLTE(png_ptr, info_ptr, &palette->front(), palette->size());
            if (palette_alpha) {
                unsigned char* alpha_data = &palette_alpha->front();
                size_t alpha_size = palette_alpha->size();
                png_set_tRNS(png_ptr, info_ptr, alpha_data, alpha_size, NULL);
            }
        }

        png_write_png(png_ptr, info_ptr, transforms, NULL);

        png_destroy_write_struct(&png_ptr, &info_ptr);
        return true;
    }

} // namespace

// Returns true if each channel of the given two colors are "close." This is
// used for comparing colors where rounding errors may cause off-by-one.
bool ColorsClose(uint32_t a, uint32_t b)
{
    return abs(static_cast<int>(SkColorGetB(a) - SkColorGetB(b))) < 2 && abs(static_cast<int>(SkColorGetG(a) - SkColorGetG(b))) < 2 && abs(static_cast<int>(SkColorGetR(a) - SkColorGetR(b))) < 2 && abs(static_cast<int>(SkColorGetA(a) - SkColorGetA(b))) < 2;
}

// Returns true if the RGB components are "close."
bool NonAlphaColorsClose(uint32_t a, uint32_t b)
{
    return abs(static_cast<int>(SkColorGetB(a) - SkColorGetB(b))) < 2 && abs(static_cast<int>(SkColorGetG(a) - SkColorGetG(b))) < 2 && abs(static_cast<int>(SkColorGetR(a) - SkColorGetR(b))) < 2;
}

// Returns true if the BGRA 32-bit SkColor specified by |a| is equivalent to the
// 8-bit Gray color specified by |b|.
bool BGRAGrayEqualsA8Gray(uint32_t a, uint8_t b)
{
    return SkColorGetB(a) == b && SkColorGetG(a) == b && SkColorGetR(a) == b && SkColorGetA(a) == 255;
}

void MakeTestBGRASkBitmap(int w, int h, SkBitmap* bmp)
{
    bmp->allocN32Pixels(w, h);

    uint32_t* src_data = bmp->getAddr32(0, 0);
    for (int i = 0; i < w * h; i++)
        src_data[i] = SkPreMultiplyARGB(i % 255, i % 250, i % 245, i % 240);
}

void MakeTestA8SkBitmap(int w, int h, SkBitmap* bmp)
{
    bmp->allocPixels(SkImageInfo::MakeA8(w, h));

    uint8_t* src_data = bmp->getAddr8(0, 0);
    for (int i = 0; i < w * h; i++)
        src_data[i] = i % 255;
}

TEST(PNGCodec, EncodeDecodeRGB)
{
    const int w = 20, h = 20;

    // create an image with known values
    std::vector<unsigned char> original;
    MakeRGBImage(w, h, &original);

    // encode
    std::vector<unsigned char> encoded;
    ASSERT_TRUE(PNGCodec::Encode(&original[0], PNGCodec::FORMAT_RGB,
        Size(w, h), w * 3, false,
        std::vector<PNGCodec::Comment>(),
        &encoded));

    // decode, it should have the same size as the original
    std::vector<unsigned char> decoded;
    int outw, outh;
    ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
        PNGCodec::FORMAT_RGB, &decoded,
        &outw, &outh));
    ASSERT_EQ(w, outw);
    ASSERT_EQ(h, outh);
    ASSERT_EQ(original.size(), decoded.size());

    // Images must be equal
    ASSERT_TRUE(original == decoded);
}

TEST(PNGCodec, EncodeDecodeRGBA)
{
    const int w = 20, h = 20;

    // create an image with known values, a must be opaque because it will be
    // lost during encoding
    std::vector<unsigned char> original;
    MakeRGBAImage(w, h, true, &original);

    // encode
    std::vector<unsigned char> encoded;
    ASSERT_TRUE(PNGCodec::Encode(&original[0], PNGCodec::FORMAT_RGBA,
        Size(w, h), w * 4, false,
        std::vector<PNGCodec::Comment>(),
        &encoded));

    // decode, it should have the same size as the original
    std::vector<unsigned char> decoded;
    int outw, outh;
    ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
        PNGCodec::FORMAT_RGBA, &decoded,
        &outw, &outh));
    ASSERT_EQ(w, outw);
    ASSERT_EQ(h, outh);
    ASSERT_EQ(original.size(), decoded.size());

    // Images must be exactly equal
    ASSERT_TRUE(original == decoded);
}

TEST(PNGCodec, EncodeDecodeBGRA)
{
    const int w = 20, h = 20;

    // Create an image with known values, alpha must be opaque because it will be
    // lost during encoding.
    std::vector<unsigned char> original;
    MakeRGBAImage(w, h, true, &original);

    // Encode.
    std::vector<unsigned char> encoded;
    ASSERT_TRUE(PNGCodec::Encode(&original[0], PNGCodec::FORMAT_BGRA,
        Size(w, h), w * 4, false,
        std::vector<PNGCodec::Comment>(),
        &encoded));

    // Decode, it should have the same size as the original.
    std::vector<unsigned char> decoded;
    int outw, outh;
    ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
        PNGCodec::FORMAT_BGRA, &decoded,
        &outw, &outh));
    ASSERT_EQ(w, outw);
    ASSERT_EQ(h, outh);
    ASSERT_EQ(original.size(), decoded.size());

    // Images must be exactly equal.
    ASSERT_TRUE(original == decoded);
}

TEST(PNGCodec, DecodePalette)
{
    const int w = 20, h = 20;

    // create an image with known values
    std::vector<unsigned char> original;
    std::vector<png_color> original_palette;
    std::vector<unsigned char> original_trans_chunk;
    MakePaletteImage(w, h, &original, &original_palette, &original_trans_chunk);

    // encode
    std::vector<unsigned char> encoded;
    ASSERT_TRUE(EncodeImage(original,
        w, h,
        COLOR_TYPE_PALETTE,
        &encoded,
        PNG_INTERLACE_NONE,
        &original_palette,
        &original_trans_chunk));

    // decode
    std::vector<unsigned char> decoded;
    int outw, outh;
    ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
        PNGCodec::FORMAT_RGBA, &decoded,
        &outw, &outh));
    ASSERT_EQ(w, outw);
    ASSERT_EQ(h, outh);
    ASSERT_EQ(decoded.size(), w * h * 4U);

    // Images must be equal
    for (int y = 0; y < h; ++y) {
        for (int x = 0; x < w; ++x) {
            unsigned char palette_pixel = original[y * w + x];
            png_color& palette_color = original_palette[palette_pixel];
            int alpha = original_trans_chunk[palette_pixel];
            unsigned char* rgba_pixel = &decoded[(y * w + x) * 4];

            EXPECT_EQ(palette_color.red, rgba_pixel[0]);
            EXPECT_EQ(palette_color.green, rgba_pixel[1]);
            EXPECT_EQ(palette_color.blue, rgba_pixel[2]);
            EXPECT_EQ(alpha, rgba_pixel[3]);
        }
    }
}

TEST(PNGCodec, DecodePaletteDiscardAlpha)
{
    const int w = 20, h = 20;

    // create an image with known values
    std::vector<unsigned char> original;
    std::vector<png_color> original_palette;
    std::vector<unsigned char> original_trans_chunk;
    MakePaletteImage(w, h, &original, &original_palette, &original_trans_chunk);

    // encode
    std::vector<unsigned char> encoded;
    ASSERT_TRUE(EncodeImage(original,
        w, h,
        COLOR_TYPE_PALETTE,
        &encoded,
        PNG_INTERLACE_NONE,
        &original_palette,
        &original_trans_chunk));

    // decode
    std::vector<unsigned char> decoded;
    int outw, outh;
    ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
        PNGCodec::FORMAT_RGB, &decoded,
        &outw, &outh));
    ASSERT_EQ(w, outw);
    ASSERT_EQ(h, outh);
    ASSERT_EQ(decoded.size(), w * h * 3U);

    // Images must be equal
    for (int y = 0; y < h; ++y) {
        for (int x = 0; x < w; ++x) {
            unsigned char palette_pixel = original[y * w + x];
            png_color& palette_color = original_palette[palette_pixel];
            unsigned char* rgba_pixel = &decoded[(y * w + x) * 3];

            EXPECT_EQ(palette_color.red, rgba_pixel[0]);
            EXPECT_EQ(palette_color.green, rgba_pixel[1]);
            EXPECT_EQ(palette_color.blue, rgba_pixel[2]);
        }
    }
}

TEST(PNGCodec, DecodeInterlacedPalette)
{
    const int w = 20, h = 20;

    // create an image with known values
    std::vector<unsigned char> original;
    std::vector<png_color> original_palette;
    std::vector<unsigned char> original_trans_chunk;
    MakePaletteImage(w, h, &original, &original_palette, &original_trans_chunk);

    // encode
    std::vector<unsigned char> encoded;
    ASSERT_TRUE(EncodeImage(original,
        w, h,
        COLOR_TYPE_PALETTE,
        &encoded,
        PNG_INTERLACE_ADAM7,
        &original_palette,
        &original_trans_chunk));

    // decode
    std::vector<unsigned char> decoded;
    int outw, outh;
    ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
        PNGCodec::FORMAT_RGBA, &decoded,
        &outw, &outh));
    ASSERT_EQ(w, outw);
    ASSERT_EQ(h, outh);
    ASSERT_EQ(decoded.size(), w * h * 4U);

    // Images must be equal
    for (int y = 0; y < h; ++y) {
        for (int x = 0; x < w; ++x) {
            unsigned char palette_pixel = original[y * w + x];
            png_color& palette_color = original_palette[palette_pixel];
            int alpha = original_trans_chunk[palette_pixel];
            unsigned char* rgba_pixel = &decoded[(y * w + x) * 4];

            EXPECT_EQ(palette_color.red, rgba_pixel[0]);
            EXPECT_EQ(palette_color.green, rgba_pixel[1]);
            EXPECT_EQ(palette_color.blue, rgba_pixel[2]);
            EXPECT_EQ(alpha, rgba_pixel[3]);
        }
    }
}

TEST(PNGCodec, DecodeGrayscale)
{
    const int w = 20, h = 20;

    // create an image with known values
    std::vector<unsigned char> original;
    MakeGrayscaleImage(w, h, &original);

    // encode
    std::vector<unsigned char> encoded;
    ASSERT_TRUE(EncodeImage(original, w, h, COLOR_TYPE_GRAY, &encoded));

    // decode
    std::vector<unsigned char> decoded;
    int outw, outh;
    ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
        PNGCodec::FORMAT_RGB, &decoded,
        &outw, &outh));
    ASSERT_EQ(w, outw);
    ASSERT_EQ(h, outh);
    ASSERT_EQ(decoded.size(), original.size() * 3);

    // Images must be equal
    for (int y = 0; y < h; ++y) {
        for (int x = 0; x < w; ++x) {
            unsigned char gray_pixel = original[(y * w + x)];
            unsigned char* rgba_pixel = &decoded[(y * w + x) * 3];
            EXPECT_EQ(rgba_pixel[0], gray_pixel);
            EXPECT_EQ(rgba_pixel[1], gray_pixel);
            EXPECT_EQ(rgba_pixel[2], gray_pixel);
        }
    }
}

TEST(PNGCodec, DecodeGrayscaleWithAlpha)
{
    const int w = 20, h = 20;

    // create an image with known values
    std::vector<unsigned char> original;
    MakeGrayscaleAlphaImage(w, h, &original);

    // encode
    std::vector<unsigned char> encoded;
    ASSERT_TRUE(EncodeImage(original,
        w, h,
        COLOR_TYPE_GRAY_ALPHA,
        &encoded));

    // decode
    std::vector<unsigned char> decoded;
    int outw, outh;
    ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
        PNGCodec::FORMAT_RGBA, &decoded,
        &outw, &outh));
    ASSERT_EQ(w, outw);
    ASSERT_EQ(h, outh);
    ASSERT_EQ(decoded.size(), original.size() * 2);

    // Images must be equal
    for (int y = 0; y < h; ++y) {
        for (int x = 0; x < w; ++x) {
            unsigned char* gray_pixel = &original[(y * w + x) * 2];
            unsigned char* rgba_pixel = &decoded[(y * w + x) * 4];
            EXPECT_EQ(rgba_pixel[0], gray_pixel[0]);
            EXPECT_EQ(rgba_pixel[1], gray_pixel[0]);
            EXPECT_EQ(rgba_pixel[2], gray_pixel[0]);
            EXPECT_EQ(rgba_pixel[3], gray_pixel[1]);
        }
    }
}

TEST(PNGCodec, DecodeGrayscaleWithAlphaDiscardAlpha)
{
    const int w = 20, h = 20;

    // create an image with known values
    std::vector<unsigned char> original;
    MakeGrayscaleAlphaImage(w, h, &original);

    // encode
    std::vector<unsigned char> encoded;
    ASSERT_TRUE(EncodeImage(original,
        w, h,
        COLOR_TYPE_GRAY_ALPHA,
        &encoded));

    // decode
    std::vector<unsigned char> decoded;
    int outw, outh;
    ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
        PNGCodec::FORMAT_RGB, &decoded,
        &outw, &outh));
    ASSERT_EQ(w, outw);
    ASSERT_EQ(h, outh);
    ASSERT_EQ(decoded.size(), w * h * 3U);

    // Images must be equal
    for (int y = 0; y < h; ++y) {
        for (int x = 0; x < w; ++x) {
            unsigned char* gray_pixel = &original[(y * w + x) * 2];
            unsigned char* rgba_pixel = &decoded[(y * w + x) * 3];
            EXPECT_EQ(rgba_pixel[0], gray_pixel[0]);
            EXPECT_EQ(rgba_pixel[1], gray_pixel[0]);
            EXPECT_EQ(rgba_pixel[2], gray_pixel[0]);
        }
    }
}

TEST(PNGCodec, DecodeInterlacedGrayscale)
{
    const int w = 20, h = 20;

    // create an image with known values
    std::vector<unsigned char> original;
    MakeGrayscaleImage(w, h, &original);

    // encode
    std::vector<unsigned char> encoded;
    ASSERT_TRUE(EncodeImage(original,
        w, h,
        COLOR_TYPE_GRAY,
        &encoded,
        PNG_INTERLACE_ADAM7));

    // decode
    std::vector<unsigned char> decoded;
    int outw, outh;
    ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
        PNGCodec::FORMAT_RGBA, &decoded,
        &outw, &outh));
    ASSERT_EQ(w, outw);
    ASSERT_EQ(h, outh);
    ASSERT_EQ(decoded.size(), original.size() * 4);

    // Images must be equal
    for (int y = 0; y < h; ++y) {
        for (int x = 0; x < w; ++x) {
            unsigned char gray_pixel = original[(y * w + x)];
            unsigned char* rgba_pixel = &decoded[(y * w + x) * 4];
            EXPECT_EQ(rgba_pixel[0], gray_pixel);
            EXPECT_EQ(rgba_pixel[1], gray_pixel);
            EXPECT_EQ(rgba_pixel[2], gray_pixel);
            EXPECT_EQ(rgba_pixel[3], 0xFF);
        }
    }
}

TEST(PNGCodec, DecodeInterlacedGrayscaleWithAlpha)
{
    const int w = 20, h = 20;

    // create an image with known values
    std::vector<unsigned char> original;
    MakeGrayscaleAlphaImage(w, h, &original);

    // encode
    std::vector<unsigned char> encoded;
    ASSERT_TRUE(EncodeImage(original,
        w, h,
        COLOR_TYPE_GRAY_ALPHA,
        &encoded,
        PNG_INTERLACE_ADAM7));

    // decode
    std::vector<unsigned char> decoded;
    int outw, outh;
    ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
        PNGCodec::FORMAT_RGBA, &decoded,
        &outw, &outh));
    ASSERT_EQ(w, outw);
    ASSERT_EQ(h, outh);
    ASSERT_EQ(decoded.size(), original.size() * 2);

    // Images must be equal
    for (int y = 0; y < h; ++y) {
        for (int x = 0; x < w; ++x) {
            unsigned char* gray_pixel = &original[(y * w + x) * 2];
            unsigned char* rgba_pixel = &decoded[(y * w + x) * 4];
            EXPECT_EQ(rgba_pixel[0], gray_pixel[0]);
            EXPECT_EQ(rgba_pixel[1], gray_pixel[0]);
            EXPECT_EQ(rgba_pixel[2], gray_pixel[0]);
            EXPECT_EQ(rgba_pixel[3], gray_pixel[1]);
        }
    }
}

TEST(PNGCodec, DecodeInterlacedRGB)
{
    const int w = 20, h = 20;

    // create an image with known values
    std::vector<unsigned char> original;
    MakeRGBImage(w, h, &original);

    // encode
    std::vector<unsigned char> encoded;
    ASSERT_TRUE(EncodeImage(original,
        w, h,
        COLOR_TYPE_RGB,
        &encoded,
        PNG_INTERLACE_ADAM7));

    // decode, it should have the same size as the original
    std::vector<unsigned char> decoded;
    int outw, outh;
    ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
        PNGCodec::FORMAT_RGB, &decoded,
        &outw, &outh));
    ASSERT_EQ(w, outw);
    ASSERT_EQ(h, outh);
    ASSERT_EQ(original.size(), decoded.size());

    // Images must be equal
    ASSERT_EQ(original, decoded);
}

TEST(PNGCodec, DecodeInterlacedRGBA)
{
    const int w = 20, h = 20;

    // create an image with known values
    std::vector<unsigned char> original;
    MakeRGBAImage(w, h, false, &original);

    // encode
    std::vector<unsigned char> encoded;
    ASSERT_TRUE(EncodeImage(original,
        w, h,
        COLOR_TYPE_RGBA,
        &encoded,
        PNG_INTERLACE_ADAM7));

    // decode, it should have the same size as the original
    std::vector<unsigned char> decoded;
    int outw, outh;
    ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
        PNGCodec::FORMAT_RGBA, &decoded,
        &outw, &outh));
    ASSERT_EQ(w, outw);
    ASSERT_EQ(h, outh);
    ASSERT_EQ(original.size(), decoded.size());

    // Images must be equal
    ASSERT_EQ(original, decoded);
}

TEST(PNGCodec, DecodeInterlacedRGBADiscardAlpha)
{
    const int w = 20, h = 20;

    // create an image with known values
    std::vector<unsigned char> original;
    MakeRGBAImage(w, h, false, &original);

    // encode
    std::vector<unsigned char> encoded;
    ASSERT_TRUE(EncodeImage(original,
        w, h,
        COLOR_TYPE_RGBA,
        &encoded,
        PNG_INTERLACE_ADAM7));

    // decode
    std::vector<unsigned char> decoded;
    int outw, outh;
    ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
        PNGCodec::FORMAT_RGB, &decoded,
        &outw, &outh));
    ASSERT_EQ(w, outw);
    ASSERT_EQ(h, outh);
    ASSERT_EQ(decoded.size(), w * h * 3U);

    // Images must be equal
    for (int x = 0; x < w; x++) {
        for (int y = 0; y < h; y++) {
            unsigned char* orig_px = &original[(y * w + x) * 4];
            unsigned char* dec_px = &decoded[(y * w + x) * 3];
            EXPECT_EQ(dec_px[0], orig_px[0]);
            EXPECT_EQ(dec_px[1], orig_px[1]);
            EXPECT_EQ(dec_px[2], orig_px[2]);
        }
    }
}

TEST(PNGCodec, DecodeInterlacedBGR)
{
    const int w = 20, h = 20;

    // create an image with known values
    std::vector<unsigned char> original;
    MakeRGBImage(w, h, &original);

    // encode
    std::vector<unsigned char> encoded;
    ASSERT_TRUE(EncodeImage(original,
        w, h,
        COLOR_TYPE_BGR,
        &encoded,
        PNG_INTERLACE_ADAM7));

    // decode, it should have the same size as the original
    std::vector<unsigned char> decoded;
    int outw, outh;
    ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
        PNGCodec::FORMAT_BGRA, &decoded,
        &outw, &outh));
    ASSERT_EQ(w, outw);
    ASSERT_EQ(h, outh);
    ASSERT_EQ(decoded.size(), w * h * 4U);

    // Images must be equal
    for (int x = 0; x < w; x++) {
        for (int y = 0; y < h; y++) {
            unsigned char* orig_px = &original[(y * w + x) * 3];
            unsigned char* dec_px = &decoded[(y * w + x) * 4];
            EXPECT_EQ(dec_px[0], orig_px[0]);
            EXPECT_EQ(dec_px[1], orig_px[1]);
            EXPECT_EQ(dec_px[2], orig_px[2]);
        }
    }
}

TEST(PNGCodec, DecodeInterlacedBGRA)
{
    const int w = 20, h = 20;

    // create an image with known values
    std::vector<unsigned char> original;
    MakeRGBAImage(w, h, false, &original);

    // encode
    std::vector<unsigned char> encoded;
    ASSERT_TRUE(EncodeImage(original,
        w, h,
        COLOR_TYPE_BGRA,
        &encoded,
        PNG_INTERLACE_ADAM7));

    // decode, it should have the same size as the original
    std::vector<unsigned char> decoded;
    int outw, outh;
    ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
        PNGCodec::FORMAT_BGRA, &decoded,
        &outw, &outh));
    ASSERT_EQ(w, outw);
    ASSERT_EQ(h, outh);
    ASSERT_EQ(original.size(), decoded.size());

    // Images must be equal
    ASSERT_EQ(original, decoded);
}

// Not encoding an interlaced PNG from SkBitmap because we don't do it
// anywhere, and the ability to do that requires more code changes.
TEST(PNGCodec, DecodeInterlacedRGBtoSkBitmap)
{
    const int w = 20, h = 20;

    // create an image with known values
    std::vector<unsigned char> original;
    MakeRGBImage(w, h, &original);

    // encode
    std::vector<unsigned char> encoded;
    ASSERT_TRUE(EncodeImage(original,
        w, h,
        COLOR_TYPE_RGB,
        &encoded,
        PNG_INTERLACE_ADAM7));

    // Decode the encoded string.
    SkBitmap decoded_bitmap;
    ASSERT_TRUE(PNGCodec::Decode(&encoded.front(), encoded.size(),
        &decoded_bitmap));

    for (int x = 0; x < w; x++) {
        for (int y = 0; y < h; y++) {
            const unsigned char* original_pixel = &original[(y * w + x) * 3];
            const uint32_t original_pixel_sk = SkPackARGB32(0xFF,
                original_pixel[0],
                original_pixel[1],
                original_pixel[2]);
            const uint32_t decoded_pixel = decoded_bitmap.getAddr32(0, y)[x];
            EXPECT_EQ(original_pixel_sk, decoded_pixel);
        }
    }
}

TEST(PNGCodec, DecodeInterlacedRGBAtoSkBitmap)
{
    const int w = 20, h = 20;

    // create an image with known values
    std::vector<unsigned char> original;
    MakeRGBAImage(w, h, false, &original);

    // encode
    std::vector<unsigned char> encoded;
    ASSERT_TRUE(EncodeImage(original,
        w, h,
        COLOR_TYPE_RGBA,
        &encoded,
        PNG_INTERLACE_ADAM7));

    // Decode the encoded string.
    SkBitmap decoded_bitmap;
    ASSERT_TRUE(PNGCodec::Decode(&encoded.front(), encoded.size(),
        &decoded_bitmap));

    for (int x = 0; x < w; x++) {
        for (int y = 0; y < h; y++) {
            const unsigned char* original_pixel = &original[(y * w + x) * 4];
            const uint32_t original_pixel_sk = SkPackARGB32(original_pixel[3],
                original_pixel[0],
                original_pixel[1],
                original_pixel[2]);
            const uint32_t decoded_pixel = decoded_bitmap.getAddr32(0, y)[x];
            EXPECT_EQ(original_pixel_sk, decoded_pixel);
        }
    }
}

// Test that corrupted data decompression causes failures.
TEST(PNGCodec, DecodeCorrupted)
{
    int w = 20, h = 20;

    // Make some random data (an uncompressed image).
    std::vector<unsigned char> original;
    MakeRGBImage(w, h, &original);

    // It should fail when given non-JPEG compressed data.
    std::vector<unsigned char> output;
    int outw, outh;
    EXPECT_FALSE(PNGCodec::Decode(&original[0], original.size(),
        PNGCodec::FORMAT_RGB, &output,
        &outw, &outh));

    // Make some compressed data.
    std::vector<unsigned char> compressed;
    ASSERT_TRUE(PNGCodec::Encode(&original[0], PNGCodec::FORMAT_RGB,
        Size(w, h), w * 3, false,
        std::vector<PNGCodec::Comment>(),
        &compressed));

    // Try decompressing a truncated version.
    EXPECT_FALSE(PNGCodec::Decode(&compressed[0], compressed.size() / 2,
        PNGCodec::FORMAT_RGB, &output,
        &outw, &outh));

    // Corrupt it and try decompressing that.
    for (int i = 10; i < 30; i++)
        compressed[i] = i;
    EXPECT_FALSE(PNGCodec::Decode(&compressed[0], compressed.size(),
        PNGCodec::FORMAT_RGB, &output,
        &outw, &outh));
}

TEST(PNGCodec, StripAddAlpha)
{
    const int w = 20, h = 20;

    // These should be the same except one has a 0xff alpha channel.
    std::vector<unsigned char> original_rgb;
    MakeRGBImage(w, h, &original_rgb);
    std::vector<unsigned char> original_rgba;
    MakeRGBAImage(w, h, false, &original_rgba);

    // Encode RGBA data as RGB.
    std::vector<unsigned char> encoded;
    EXPECT_TRUE(PNGCodec::Encode(&original_rgba[0], PNGCodec::FORMAT_RGBA,
        Size(w, h), w * 4, true,
        std::vector<PNGCodec::Comment>(),
        &encoded));

    // Decode the RGB to RGBA.
    std::vector<unsigned char> decoded;
    int outw, outh;
    EXPECT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
        PNGCodec::FORMAT_RGBA, &decoded,
        &outw, &outh));

    // Decoded and reference should be the same (opaque alpha).
    ASSERT_EQ(w, outw);
    ASSERT_EQ(h, outh);
    ASSERT_EQ(original_rgba.size(), decoded.size());
    ASSERT_EQ(original_rgba, decoded);

    // Encode RGBA to RGBA.
    EXPECT_TRUE(PNGCodec::Encode(&original_rgba[0], PNGCodec::FORMAT_RGBA,
        Size(w, h), w * 4, false,
        std::vector<PNGCodec::Comment>(),
        &encoded));

    // Decode the RGBA to RGB.
    EXPECT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
        PNGCodec::FORMAT_RGB, &decoded,
        &outw, &outh));

    // It should be the same as our non-alpha-channel reference.
    ASSERT_EQ(w, outw);
    ASSERT_EQ(h, outh);
    ASSERT_EQ(original_rgb.size(), decoded.size());
    ASSERT_EQ(original_rgb, decoded);
}

TEST(PNGCodec, EncodeBGRASkBitmapStridePadded)
{
    const int kWidth = 20;
    const int kHeight = 20;
    const int kPaddedWidth = 32;
    const int kBytesPerPixel = 4;
    const int kPaddedSize = kPaddedWidth * kHeight;
    const int kRowBytes = kPaddedWidth * kBytesPerPixel;

    SkImageInfo info = SkImageInfo::MakeN32Premul(kWidth, kHeight);
    SkBitmap original_bitmap;
    original_bitmap.setInfo(info, kRowBytes);
    original_bitmap.allocPixels();

    // Write data over the source bitmap.
    // We write on the pad area here too.
    // The encoder should ignore the pad area.
    uint32_t* src_data = original_bitmap.getAddr32(0, 0);
    for (int i = 0; i < kPaddedSize; i++) {
        src_data[i] = SkPreMultiplyARGB(i % 255, i % 250, i % 245, i % 240);
    }

    // Encode the bitmap.
    std::vector<unsigned char> encoded;
    PNGCodec::EncodeBGRASkBitmap(original_bitmap, false, &encoded);

    // Decode the encoded string.
    SkBitmap decoded_bitmap;
    EXPECT_TRUE(PNGCodec::Decode(&encoded.front(), encoded.size(),
        &decoded_bitmap));

    // Compare the original bitmap and the output bitmap. We use ColorsClose
    // as SkBitmaps are considered to be pre-multiplied, the unpremultiplication
    // (in Encode) and repremultiplication (in Decode) can be lossy.
    for (int x = 0; x < kWidth; x++) {
        for (int y = 0; y < kHeight; y++) {
            uint32_t original_pixel = original_bitmap.getAddr32(0, y)[x];
            uint32_t decoded_pixel = decoded_bitmap.getAddr32(0, y)[x];
            EXPECT_TRUE(ColorsClose(original_pixel, decoded_pixel));
        }
    }
}

TEST(PNGCodec, EncodeBGRASkBitmap)
{
    const int w = 20, h = 20;

    SkBitmap original_bitmap;
    MakeTestBGRASkBitmap(w, h, &original_bitmap);

    // Encode the bitmap.
    std::vector<unsigned char> encoded;
    PNGCodec::EncodeBGRASkBitmap(original_bitmap, false, &encoded);

    // Decode the encoded string.
    SkBitmap decoded_bitmap;
    EXPECT_TRUE(PNGCodec::Decode(&encoded.front(), encoded.size(),
        &decoded_bitmap));

    // Compare the original bitmap and the output bitmap. We use ColorsClose
    // as SkBitmaps are considered to be pre-multiplied, the unpremultiplication
    // (in Encode) and repremultiplication (in Decode) can be lossy.
    for (int x = 0; x < w; x++) {
        for (int y = 0; y < h; y++) {
            uint32_t original_pixel = original_bitmap.getAddr32(0, y)[x];
            uint32_t decoded_pixel = decoded_bitmap.getAddr32(0, y)[x];
            EXPECT_TRUE(ColorsClose(original_pixel, decoded_pixel));
        }
    }
}

TEST(PNGCodec, EncodeA8SkBitmap)
{
    const int w = 20, h = 20;

    SkBitmap original_bitmap;
    MakeTestA8SkBitmap(w, h, &original_bitmap);

    // Encode the bitmap.
    std::vector<unsigned char> encoded;
    EXPECT_TRUE(PNGCodec::EncodeA8SkBitmap(original_bitmap, &encoded));

    // Decode the encoded string.
    SkBitmap decoded_bitmap;
    EXPECT_TRUE(PNGCodec::Decode(&encoded.front(), encoded.size(),
        &decoded_bitmap));

    for (int x = 0; x < w; x++) {
        for (int y = 0; y < h; y++) {
            uint8_t original_pixel = *original_bitmap.getAddr8(x, y);
            uint32_t decoded_pixel = *decoded_bitmap.getAddr32(x, y);
            EXPECT_TRUE(BGRAGrayEqualsA8Gray(decoded_pixel, original_pixel));
        }
    }
}

TEST(PNGCodec, EncodeBGRASkBitmapDiscardTransparency)
{
    const int w = 20, h = 20;

    SkBitmap original_bitmap;
    MakeTestBGRASkBitmap(w, h, &original_bitmap);

    // Encode the bitmap.
    std::vector<unsigned char> encoded;
    PNGCodec::EncodeBGRASkBitmap(original_bitmap, true, &encoded);

    // Decode the encoded string.
    SkBitmap decoded_bitmap;
    EXPECT_TRUE(PNGCodec::Decode(&encoded.front(), encoded.size(),
        &decoded_bitmap));

    // Compare the original bitmap and the output bitmap. We need to
    // unpremultiply original_pixel, as the decoded bitmap doesn't have an alpha
    // channel.
    for (int x = 0; x < w; x++) {
        for (int y = 0; y < h; y++) {
            uint32_t original_pixel = original_bitmap.getAddr32(0, y)[x];
            uint32_t unpremultiplied = SkUnPreMultiply::PMColorToColor(original_pixel);
            uint32_t decoded_pixel = decoded_bitmap.getAddr32(0, y)[x];
            uint32_t unpremultiplied_decoded = SkUnPreMultiply::PMColorToColor(decoded_pixel);

            EXPECT_TRUE(NonAlphaColorsClose(unpremultiplied, unpremultiplied_decoded))
                << "Original_pixel: ("
                << SkColorGetR(unpremultiplied) << ", "
                << SkColorGetG(unpremultiplied) << ", "
                << SkColorGetB(unpremultiplied) << "), "
                << "Decoded pixel: ("
                << SkColorGetR(unpremultiplied_decoded) << ", "
                << SkColorGetG(unpremultiplied_decoded) << ", "
                << SkColorGetB(unpremultiplied_decoded) << ")";
        }
    }
}

TEST(PNGCodec, EncodeWithComment)
{
    const int w = 10, h = 10;

    std::vector<unsigned char> original;
    MakeRGBImage(w, h, &original);

    std::vector<unsigned char> encoded;
    std::vector<PNGCodec::Comment> comments;
    comments.push_back(PNGCodec::Comment("key", "text"));
    comments.push_back(PNGCodec::Comment("test", "something"));
    comments.push_back(PNGCodec::Comment("have some", "spaces in both"));
    EXPECT_TRUE(PNGCodec::Encode(&original[0], PNGCodec::FORMAT_RGB,
        Size(w, h), w * 3, false, comments, &encoded));

    // Each chunk is of the form length (4 bytes), chunk type (tEXt), data,
    // checksum (4 bytes).  Make sure we find all of them in the encoded
    // results.
    const unsigned char kExpected1[] = "\x00\x00\x00\x08tEXtkey\x00text\x9e\xe7\x66\x51";
    const unsigned char kExpected2[] = "\x00\x00\x00\x0etEXttest\x00something\x29\xba\xef\xac";
    const unsigned char kExpected3[] = "\x00\x00\x00\x18tEXthave some\x00spaces in both\x8d\x69\x34\x2d";

    EXPECT_NE(std::search(encoded.begin(), encoded.end(), kExpected1,
                  kExpected1 + arraysize(kExpected1)),
        encoded.end());
    EXPECT_NE(std::search(encoded.begin(), encoded.end(), kExpected2,
                  kExpected2 + arraysize(kExpected2)),
        encoded.end());
    EXPECT_NE(std::search(encoded.begin(), encoded.end(), kExpected3,
                  kExpected3 + arraysize(kExpected3)),
        encoded.end());
}

TEST(PNGCodec, EncodeDecodeWithVaryingCompressionLevels)
{
    const int w = 20, h = 20;

    // create an image with known values, a must be opaque because it will be
    // lost during encoding
    SkBitmap original_bitmap;
    MakeTestBGRASkBitmap(w, h, &original_bitmap);

    // encode
    std::vector<unsigned char> encoded_normal;
    EXPECT_TRUE(
        PNGCodec::EncodeBGRASkBitmap(original_bitmap, false, &encoded_normal));

    std::vector<unsigned char> encoded_fast;
    EXPECT_TRUE(
        PNGCodec::FastEncodeBGRASkBitmap(original_bitmap, false, &encoded_fast));

    // Make sure the different compression settings actually do something; the
    // sizes should be different.
    EXPECT_NE(encoded_normal.size(), encoded_fast.size());

    // decode, they should be identical to the original.
    SkBitmap decoded;
    EXPECT_TRUE(
        PNGCodec::Decode(&encoded_normal[0], encoded_normal.size(), &decoded));
    EXPECT_TRUE(BitmapsAreEqual(decoded, original_bitmap));

    EXPECT_TRUE(
        PNGCodec::Decode(&encoded_fast[0], encoded_fast.size(), &decoded));
    EXPECT_TRUE(BitmapsAreEqual(decoded, original_bitmap));
}

} // namespace gfx
